package sc;

import java.awt.Color;
import java.awt.Graphics;

import sc.math.SimplexNoise;

/**
 * Racetrack2DGenerator provides methods to create instances of Racetrack2D that
 * represent a "drivable" track. Each instance is reproducible using its unique
 * numeric seed.
 */
public final class Racetrack2DGenerator {
	
	// Bounds of racetrack area
	private final double[] bounds = new double[4];
	
	// Current seed
	private int seed;
	
	// Size of noise cloud
	private final int samplesize = 500;
	private final float halfsamplesize = this.samplesize * 0.5f;
	private final float quartersamplesize = this.halfsamplesize * 0.5f;
	
	private final double frequency = 0.005;
	private final double amplitude = 1;
	private final int interval = 5;
	private final boolean grid[][] = new boolean[this.samplesize][this.samplesize];

	// Radial sweep neighbour offsets
	private final int[] dx = { -1, -1, 0, 1, 1, 1, 0, -1, };
	private final int[] dy = { 0, 1, 1, 1, 0, -1, -1, -1, };
	
	/** Creates an instance of Racetrack2DGenerator */
	public Racetrack2DGenerator(int left, int top, int width, int height) {
		setBounds(left, top, width, height);
	}

	/**
	 * Sets the draw bounds used by subsequent generations.
	 * 
	 * @param left
	 * 			Left x-coordinate of racetrack draw area
	 * @param top
	 * 			Top y-coordinate of racetrack draw area
	 * @param width
	 * 			Width of racetrack draw area
	 * @param height
	 * 			Height of racetrack draw area
	 */
	public void setBounds(double left, double top, double width, double height) {
		this.bounds[0] = left;
		this.bounds[1] = top;
		this.bounds[2] = width;
		this.bounds[3] = height;
	}
	
	/** Creates a new racetrack using the current seed */
	public Racetrack2D create(Graphics g) {
		Racetrack2D track;
		int x = 0, y, i = 0;
		int[] lastfalse = new int[2];
		double p, q, noise, n2;
		
		// Create a new Racetrack2D object
		track = new Racetrack2D();
		track.setWidth(this.bounds[2] * 0.1);

		// Paint a cloud of false in the middle of a field of truth
		for (y = 0; y < this.samplesize; y++) {
			for (x = 0; x < this.samplesize; x++) {

				p = x - this.halfsamplesize;
				q = y - this.halfsamplesize;

				// Centred circle radiating out from -1 in the middle to 1 at sample edge
				noise = (Math.sqrt(p * p + q * q) / this.quartersamplesize) - 1;
				
				//noise *= (noise * noise * noise * noise * noise * noise);
				
				n2 = SimplexNoise.noise(x * this.frequency, y * this.frequency, this.seed)
						* this.amplitude;
				
				if (n2 < 0) noise += n2;
				//noise += n2;
				//noise += SimplexNoise.noise(x * this.frequency / 2, y * this.frequency / 2, this.seed + 5)
				//* this.amplitude * 2;
				noise = (noise + 1) / 2;
				if (noise < 0) noise = 0;
				if (noise > 1) noise = 1;
				g.setColor(new Color((float)noise, (float)noise, (float)noise));
				
				/*if (noise > 0) {
					this.grid[x][y] = true;
					g.setColor(new Color(0x333333));
				} else {
					this.grid[x][y] = false;
					g.setColor(new Color(0x000000));
					lastfalse[0] = x;
					lastfalse[1] = y;
				}*/

				g.drawRect((int)this.bounds[0] + x, (int)this.bounds[1] + y, 1, 1);
			}
		}
		
		x++;
		
		// Traces the boundary of the false cloud using a radial sweep algorithm
		/*do {
			track.appendControlPoint(x, y);

			g.setColor(new Color(0x555555));
			g.drawRect(x, y, 1, 1);

			for (int t = 0; t < this.interval; t++) {
				while (!this.grid[x + this.dx[i]][y + this.dy[i]]) {
					i = (i == 7) ? 0 : i + 1;
				}
				x += this.dx[i];
				y += this.dy[i];
				if (x == lastfalse[0] && y == lastfalse[1])
					break;
				i = (i < 2) ? 8 + (i - 2) : i - 2;
			}

		} while (x != lastfalse[0] && y != lastfalse[1]);*/
		
		this.seed++;
			
		// Scale and centre the track spline to the current boundary
		scaleCentre(track);
		track.update();
		
		return track;
	}

	/** Scales and centres the track within the given boundaries. */
	private void scaleCentre(Racetrack2D track) {
		if (track.getSpline().points.size() < 4) return;
		
		double[] point;
		double[] centre = new double[2];
		double[] bound = new double[4];
		double[] dist = new double[2];
		double[] farthest = new double[2];
		double[] scale = new double[2];
		double[] updcentre = new double[2];
		int i;

		// Initially define our bounds as a single point
		point = track.getSpline().points.get(0);
		bound[0] = point[0];
		bound[1] = point[0];
		bound[2] = point[1];
		bound[3] = point[1];

		// Only scale and centre control points 0 to n - 3, as last three are
		// also points 0, 1, 2 (don't want to get scaled twice)
		int last = track.getSpline().points.size() - 3;
		for (i = 0; i < last; i++) {
			point = track.getSpline().points.get(i);
			if (point[0] < bound[0])
				bound[0] = point[0];
			if (point[0] > bound[1])
				bound[1] = point[0];
			if (point[1] < bound[2])
				bound[2] = point[1];
			if (point[1] > bound[3])
				bound[3] = point[1];
		}

		// Calculate centre coordinate of the splines control point cloud
		centre[0] = (bound[0] + bound[1]) / 2;
		centre[1] = (bound[2] + bound[3]) / 2;

		// Calculate farthest x and y coordinates
		dist[0] = centre[0] - bound[0];
		dist[1] = bound[1] - centre[0];
		farthest[0] = (dist[0] > dist[1]) ? dist[0] : dist[1];

		dist[0] = centre[1] - bound[2];
		dist[1] = bound[3] - centre[1];
		farthest[1] = (dist[0] > dist[1]) ? dist[0] : dist[1];

		// Calculate scaling factor
		scale[0] = (this.bounds[2] / 2 - track.getWidth()) / farthest[0];
		scale[1] = (this.bounds[3] / 2 - track.getWidth()) / farthest[1];

		// Calculate centre of draw area
		updcentre[0] = this.bounds[0] + this.bounds[2] / 2;
		updcentre[1] = this.bounds[1] + this.bounds[3] / 2;

		// Move all spline control points to new position
		for (i = 0; i < last; i++) {
			point = track.getSpline().points.get(i);
			point[0] = (int) (updcentre[0] + (point[0] - centre[0]) * scale[0]);
			point[1] = (int) (updcentre[1] + (point[1] - centre[1]) * scale[1]);
		}
	}

}
